Implement platform-specific dependencies
authorPierre Krieger <pierre.krieger1708@gmail.com>
Tue, 21 Oct 2014 16:36:55 +0000 (18:36 +0200)
committerPierre Krieger <pierre.krieger1708@gmail.com>
Tue, 28 Oct 2014 08:29:41 +0000 (09:29 +0100)
src/cargo/core/dependency.rs
src/cargo/core/resolver/mod.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_rustc/context.rs
src/cargo/ops/cargo_rustc/fingerprint.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/ops/mod.rs
src/cargo/util/config.rs
src/cargo/util/toml.rs
tests/test_cargo_compile.rs

index f877cbadb37a46d39e956085f29bb811e6de6846..ad814d6e42e2b2d84c3e071cd2167fa052beee66 100644 (file)
@@ -16,6 +16,10 @@ pub struct Dependency {
     optional: bool,
     default_features: bool,
     features: Vec<String>,
+
+    // This dependency should be used only for this platform.
+    // `None` means *all platforms*.
+    only_for_platform: Option<String>,
 }
 
 impl Dependency {
@@ -57,6 +61,7 @@ impl Dependency {
             features: Vec::new(),
             default_features: true,
             specified_req: None,
+            only_for_platform: None,
         }
     }
 
@@ -121,6 +126,11 @@ impl Dependency {
             .source_id(id.get_source_id().clone())
     }
 
+    pub fn only_for_platform(mut self, platform: Option<String>) -> Dependency {
+        self.only_for_platform = platform;
+        self
+    }
+
     /// Returns false if the dependency is only used to build the local package.
     pub fn is_transitive(&self) -> bool { self.transitive }
     pub fn is_optional(&self) -> bool { self.optional }
@@ -140,6 +150,15 @@ impl Dependency {
             (self.only_match_name || (self.req.matches(id.get_version()) &&
                                       &self.source_id == id.get_source_id()))
     }
+
+    /// Returns true if the dependency should be built for this platform.
+    pub fn is_active_for_platform(&self, platform: &str) -> bool {
+        match self.only_for_platform {
+            None => true,
+            Some(ref p) if p.as_slice() == platform => true,
+            _ => false
+        }
+    }
 }
 
 #[deriving(PartialEq,Clone,Encodable)]
index 5179fc91ef46517d1bc7c68b942f0a7c6f566e9b..5931df8c7279d4c74d33f7e3cff04529e2d1a9da 100644 (file)
@@ -32,7 +32,8 @@ pub enum ResolveMethod<'a> {
     ResolveEverything,
     ResolveRequired(/* dev_deps = */ bool,
                     /* features = */ &'a [String],
-                    /* uses_default_features = */ bool),
+                    /* uses_default_features = */ bool,
+                    /* target_platform = */ Option<&'a str>),
 }
 
 impl Resolve {
@@ -150,6 +151,12 @@ fn activate<R: Registry>(mut cx: Context,
                          parent: &Summary,
                          method: ResolveMethod)
                          -> CargoResult<CargoResult<Context>> {
+    // Extracting the platform request.
+    let platform = match method {
+        ResolveRequired(_, _, _, platform) => platform,
+        ResolveEverything => None,
+    };
+
     // First, figure out our set of dependencies based on the requsted set of
     // features. This also calculates what features we're going to enable for
     // our own dependencies.
@@ -176,18 +183,19 @@ fn activate<R: Registry>(mut cx: Context,
         a.len().cmp(&b.len())
     });
 
-    activate_deps(cx, registry, parent, deps.as_slice(), 0)
+    activate_deps(cx, registry, parent, platform, deps.as_slice(), 0)
 }
 
-fn activate_deps<R: Registry>(cx: Context,
-                              registry: &mut R,
-                              parent: &Summary,
-                              deps: &[(&Dependency, Vec<Rc<Summary>>, Vec<String>)],
-                              cur: uint) -> CargoResult<CargoResult<Context>> {
+fn activate_deps<'a, R: Registry>(cx: Context,
+                                  registry: &mut R,
+                                  parent: &Summary,
+                                  platform: Option<&'a str>,
+                                  deps: &'a [(&Dependency, Vec<Rc<Summary>>, Vec<String>)],
+                                  cur: uint) -> CargoResult<CargoResult<Context>> {
     if cur == deps.len() { return Ok(Ok(cx)) }
     let (dep, ref candidates, ref features) = deps[cur];
     let method = ResolveRequired(false, features.as_slice(),
-                                  dep.uses_default_features());
+                                  dep.uses_default_features(), platform);
 
     let key = (dep.get_name().to_string(), dep.get_source_id().clone());
     let prev_active = cx.activations.find(&key)
@@ -269,7 +277,7 @@ fn activate_deps<R: Registry>(cx: Context,
                 Err(e) => { last_err = Some(e); continue }
             }
         };
-        match try!(activate_deps(my_cx, registry, parent, deps, cur + 1)) {
+        match try!(activate_deps(my_cx, registry, parent, platform, deps, cur + 1)) {
             Ok(cx) => return Ok(Ok(cx)),
             Err(e) => { last_err = Some(e); }
         }
@@ -341,12 +349,22 @@ fn resolve_features<'a>(cx: &mut Context, parent: &'a Summary,
                                                (&'a Dependency, Vec<String>)>> {
     let dev_deps = match method {
         ResolveEverything => true,
-        ResolveRequired(dev_deps, _, _) => dev_deps,
+        ResolveRequired(dev_deps, _, _, _) => dev_deps,
     };
 
     // First, filter by dev-dependencies
     let deps = parent.get_dependencies();
-    let mut deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
+    let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
+
+    // Second, ignoring dependencies that should not be compiled for this platform
+    let mut deps = deps.filter(|d| {
+        match method {
+            ResolveRequired(_, _, _, Some(ref platform)) => {
+                d.is_active_for_platform(platform.as_slice())
+            },
+            _ => true
+        }
+    });
 
     let (mut feature_deps, used_features) = try!(build_features(parent, method));
     let mut ret = HashMap::new();
@@ -419,7 +437,7 @@ fn build_features(s: &Summary, method: ResolveMethod)
                                  &mut visited));
             }
         }
-        ResolveRequired(_, requested_features, _) =>  {
+        ResolveRequired(_, requested_features, _, _) =>  {
             for feat in requested_features.iter() {
                 try!(add_feature(s, feat.as_slice(), &mut deps, &mut used,
                                  &mut visited));
@@ -427,7 +445,7 @@ fn build_features(s: &Summary, method: ResolveMethod)
         }
     }
     match method {
-        ResolveEverything | ResolveRequired(_, _, true) => {
+        ResolveEverything | ResolveRequired(_, _, true, _) => {
             if s.get_features().find_equiv(&"default").is_some() &&
                !visited.contains_equiv(&"default") {
                 try!(add_feature(s, "default", &mut deps, &mut used,
index 79ae34f559ce6aff26d5227f0b1ac18b15ddfd4e..8e44a81d810d75661646291b871470a263d302ca 100644 (file)
@@ -86,6 +86,7 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions)
 
     let (packages, resolve_with_overrides, sources) = {
         let mut config = try!(Config::new(*shell, jobs, target.clone()));
+        let rustc_host = config.rustc_host().to_string();
         let mut registry = PackageRegistry::new(&mut config);
 
         // First, resolve the package's *listed* dependencies, as well as
@@ -98,8 +99,11 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions)
         let _p = profile::start("resolving w/ overrides...");
 
         try!(registry.add_overrides(override_ids));
+
+        let platform = target.as_ref().map(|e| e.as_slice()).or(Some(rustc_host.as_slice()));
         let method = resolver::ResolveRequired(dev_deps, features.as_slice(),
-                                               !no_default_features);
+                                               !no_default_features,
+                                               platform);
         let resolved_with_overrides =
                 try!(ops::resolve_with_previous(&mut registry, package, method,
                                                 Some(&resolve), None));
index e3e22b3431aec98a7ced07d5158fa5c210edb6a8..4e8b833103cb2fbd4f5888a813b65de5e2a08303 100644 (file)
@@ -3,7 +3,7 @@ use std::collections::hashmap::{Occupied, Vacant};
 use std::str;
 
 use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target};
-use util::{mod, CargoResult, ChainError, internal, Config, profile, Require};
+use util::{mod, CargoResult, ChainError, internal, Config, profile};
 use util::human;
 
 use super::{Kind, KindPlugin, KindTarget, Compilation};
@@ -17,7 +17,6 @@ pub enum PlatformRequirement {
 }
 
 pub struct Context<'a, 'b> {
-    pub rustc_version: String,
     pub config: &'b mut Config<'b>,
     pub resolve: &'a Resolve,
     pub sources: &'a SourceMap<'b>,
@@ -27,7 +26,6 @@ pub struct Context<'a, 'b> {
     host: Layout,
     target: Option<Layout>,
     target_triple: String,
-    host_triple: String,
     host_dylib: Option<(String, String)>,
     package_set: &'a PackageSet,
     target_dylib: Option<(String, String)>,
@@ -49,13 +47,10 @@ impl<'a, 'b> Context<'a, 'b> {
             let (dylib, _) = try!(Context::filename_parts(None));
             dylib
         };
-        let (rustc_version, rustc_host) = try!(Context::rustc_version());
         let target_triple = config.target().map(|s| s.to_string());
-        let target_triple = target_triple.unwrap_or(rustc_host.clone());
+        let target_triple = target_triple.unwrap_or(config.rustc_host().to_string());
         Ok(Context {
-            rustc_version: rustc_version,
             target_triple: target_triple,
-            host_triple: rustc_host,
             env: env,
             host: host,
             target: target,
@@ -71,28 +66,6 @@ impl<'a, 'b> Context<'a, 'b> {
         })
     }
 
-    /// Run `rustc` to figure out what its current version string is.
-    ///
-    /// The second element of the tuple returned is the target triple that rustc
-    /// is a host for.
-    fn rustc_version() -> CargoResult<(String, String)> {
-        let output = try!(util::process("rustc").arg("-v").arg("verbose")
-                               .exec_with_output());
-        let output = try!(String::from_utf8(output.output).map_err(|_| {
-            internal("rustc -v didn't return utf8 output")
-        }));
-        let triple = {
-            let triple = output.as_slice().lines().filter(|l| {
-                l.starts_with("host: ")
-            }).map(|l| l.slice_from(6)).next();
-            let triple = try!(triple.require(|| {
-                internal("rustc -v didn't have a line for `host:`")
-            }));
-            triple.to_string()
-        };
-        Ok((output, triple))
-    }
-
     /// Run `rustc` to discover the dylib prefix/suffix for the target
     /// specified as well as the exe suffix
     fn filename_parts(target: Option<&str>)
@@ -204,9 +177,9 @@ impl<'a, 'b> Context<'a, 'b> {
     /// otherwise it corresponds to the target platform.
     fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
         let (triple, pair) = if kind == KindPlugin {
-            (&self.host_triple, &self.host_dylib)
+            (self.config.rustc_host(), &self.host_dylib)
         } else {
-            (&self.target_triple, &self.target_dylib)
+            (self.target_triple.as_slice(), &self.target_dylib)
         };
         match *pair {
             None => return Err(human(format!("dylib outputs are not supported \
index 64866ab3a83d1b2108bcaf0005169e955800dbf1..b5fc06694078fd78268f6caf874cbd55ecadeeab 100644 (file)
@@ -229,7 +229,7 @@ fn is_fresh(loc: &Path, new_fingerprint: &str) -> CargoResult<bool> {
 /// fingerprint.
 fn mk_fingerprint<T: Hash>(cx: &Context, data: &T) -> String {
     let hasher = SipHasher::new_with_keys(0,0);
-    util::to_hex(hasher.hash(&(&cx.rustc_version, data)))
+    util::to_hex(hasher.hash(&(cx.config.rustc_version(), data)))
 }
 
 fn calculate_target_fresh(pkg: &Package, dep_info: &Path) -> CargoResult<bool> {
index ec34c060daabe715a5e871330252e2a5517dde21..05e3c0d0a99c0167a16fae43fc007299fb19eaef 100644 (file)
@@ -5,8 +5,8 @@ use std::io::fs::PathExtensions;
 use std::os;
 
 use core::{SourceMap, Package, PackageId, PackageSet, Target, Resolve};
-use util::{CargoResult, ProcessBuilder, CargoError, human, caused_human};
-use util::{Config, internal, ChainError, Fresh, profile};
+use util::{mod, CargoResult, ProcessBuilder, CargoError, human, caused_human};
+use util::{Require, Config, internal, ChainError, Fresh, profile};
 
 use self::job::{Job, Work};
 use self::job_queue as jq;
@@ -28,6 +28,28 @@ mod layout;
 #[deriving(PartialEq, Eq)]
 pub enum Kind { KindPlugin, KindTarget }
 
+/// Run `rustc` to figure out what its current version string is.
+///
+/// The second element of the tuple returned is the target triple that rustc
+/// is a host for.
+pub fn rustc_version() -> CargoResult<(String, String)> {
+    let output = try!(util::process("rustc").arg("-v").arg("verbose")
+                           .exec_with_output());
+    let output = try!(String::from_utf8(output.output).map_err(|_| {
+        internal("rustc -v didn't return utf8 output")
+    }));
+    let triple = {
+        let triple = output.as_slice().lines().filter(|l| {
+            l.starts_with("host: ")
+        }).map(|l| l.slice_from(6)).next();
+        let triple = try!(triple.require(|| {
+            internal("rustc -v didn't have a line for `host:`")
+        }));
+        triple.to_string()
+    };
+    Ok((output, triple))
+}
+
 // This is a temporary assert that ensures the consistency of the arguments
 // given the current limitations of Cargo. The long term fix is to have each
 // Target know the absolute path to the build location.
index 5176ce75f29fc3831024e242cae519f526ca4eaa..77f43cac3e5d297e1ff2432c6bdc487db16f3fda 100644 (file)
@@ -1,7 +1,7 @@
 pub use self::cargo_clean::{clean, CleanOptions};
 pub use self::cargo_compile::{compile, compile_pkg, CompileOptions};
 pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
-pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind};
+pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_version};
 pub use self::cargo_rustc::{KindTarget, KindPlugin, Context, LayoutProxy};
 pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget};
 pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget};
index e0a57e0401bcce9f67f12393cdf34037feef9833..20c37f3c9e3a4d67fd50380a57a5067164955a90 100644 (file)
@@ -7,6 +7,7 @@ use std::string;
 use serialize::{Encodable,Encoder};
 use toml;
 use core::MultiShell;
+use ops;
 use util::{CargoResult, ChainError, Require, internal, human};
 
 use util::toml as cargo_toml;
@@ -18,6 +19,9 @@ pub struct Config<'a> {
     target: Option<string::String>,
     linker: Option<string::String>,
     ar: Option<string::String>,
+    rustc_version: string::String,
+    /// The current host and default target of rustc
+    rustc_host: string::String,
 }
 
 impl<'a> Config<'a> {
@@ -27,6 +31,9 @@ impl<'a> Config<'a> {
         if jobs == Some(0) {
             return Err(human("jobs must be at least 1"))
         }
+
+        let (rustc_version, rustc_host) = try!(ops::rustc_version());
+
         Ok(Config {
             home_path: try!(os::homedir().require(|| {
                 human("Cargo couldn't find your home directory. \
@@ -37,6 +44,8 @@ impl<'a> Config<'a> {
             target: target,
             ar: None,
             linker: None,
+            rustc_version: rustc_version,
+            rustc_host: rustc_host,
         })
     }
 
@@ -84,6 +93,16 @@ impl<'a> Config<'a> {
     pub fn ar(&self) -> Option<&str> {
         self.ar.as_ref().map(|t| t.as_slice())
     }
+
+    /// Return the output of `rustc -v verbose`
+    pub fn rustc_version(&self) -> &str {
+        self.rustc_version.as_slice()
+    }
+
+    /// Return the host platform and default target of rustc
+    pub fn rustc_host(&self) -> &str {
+        self.rustc_host.as_slice()
+    }
 }
 
 #[deriving(Eq,PartialEq,Clone,Encodable,Decodable)]
index 27082011d64bdcf808294dc9c92763c29ba12c1d..f92d498ce80efa0fb4de91fbbc64b3f606d9288f 100644 (file)
@@ -211,6 +211,7 @@ pub struct TomlManifest {
     dependencies: Option<HashMap<String, TomlDependency>>,
     dev_dependencies: Option<HashMap<String, TomlDependency>>,
     features: Option<HashMap<String, Vec<String>>>,
+    target: Option<HashMap<String, TomlPlatform>>,
 }
 
 #[deriving(Decodable, Clone, Default)]
@@ -464,8 +465,15 @@ impl TomlManifest {
             };
 
             // Collect the deps
-            try!(process_dependencies(&mut cx, false, self.dependencies.as_ref()));
-            try!(process_dependencies(&mut cx, true, self.dev_dependencies.as_ref()));
+            try!(process_dependencies(&mut cx, false, None, self.dependencies.as_ref()));
+            try!(process_dependencies(&mut cx, true, None, self.dev_dependencies.as_ref()));
+
+            if let Some(targets) = self.target.as_ref() {
+                for (name, platform) in targets.iter() {
+                    try!(process_dependencies(&mut cx, false, Some(name.clone()),
+                                              platform.dependencies.as_ref()));
+                }
+            }
         }
 
         let build = match project.build {
@@ -503,7 +511,7 @@ impl TomlManifest {
     }
 }
 
-fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool,
+fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option<String>,
                             new_deps: Option<&HashMap<String, TomlDependency>>)
                             -> CargoResult<()> {
     let dependencies = match new_deps {
@@ -544,6 +552,7 @@ fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool,
                                                 .map(|v| v.as_slice()),
                                          &new_source_id));
         let dep = dep.transitive(!dev)
+                     .only_for_platform(platform.clone())
                      .features(details.features.unwrap_or(Vec::new()))
                      .default_features(details.default_features.unwrap_or(true))
                      .optional(details.optional.unwrap_or(false));
@@ -572,6 +581,12 @@ enum TomlPathValue {
     TomlPath(Path),
 }
 
+/// Corresponds to a `target` entry, but `TomlTarget` is already used.
+#[deriving(Decodable)]
+struct TomlPlatform {
+    dependencies: Option<HashMap<String, TomlDependency>>,
+}
+
 impl TomlTarget {
     fn new() -> TomlTarget {
         TomlTarget {
index f8b777813331b92d3dcc73a5bc778eec1b5c4d39..ee2388e0a330b3803b625b678d6880c971ec3efc 100644 (file)
@@ -1715,3 +1715,121 @@ Caused by:
 
 "));
 })
+
+#[cfg(target_os = "linux")]
+test!(cargo_platform_specific_dependency {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [target.i686-unknown-linux-gnu.dependencies.bar]
+            path = "bar"
+            [target.x86_64-unknown-linux-gnu.dependencies.bar]
+            path = "bar"
+        "#)
+        .file("src/main.rs",
+              main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("bar/src/lib.rs", r#"
+            pub fn gimme() -> String {
+                "test passed".to_string()
+            }
+        "#);
+
+    p.cargo_process("build")
+        .exec_with_output()
+        .assert();
+
+    assert_that(&p.bin("foo"), existing_file());
+
+    assert_that(
+      cargo::util::process(p.bin("foo")),
+      execs().with_stdout("test passed\n"));
+})
+
+#[cfg(not(target_os = "linux"))]
+test!(cargo_platform_specific_dependency {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [target.i686-unknown-linux-gnu.dependencies.bar]
+            path = "bar"
+            [target.x86_64-unknown-linux-gnu.dependencies.bar]
+            path = "bar"
+        "#)
+        .file("src/main.rs",
+              main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("bar/src/lib.rs", r#"
+            extern crate baz;
+
+            pub fn gimme() -> String {
+                format!("")
+            }
+        "#);
+
+    assert_that(p.cargo_process("build"),
+        execs().with_status(101));
+})
+
+test!(cargo_platform_specific_dependency_wrong_platform {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [target.non-existing-triplet.dependencies.bar]
+            path = "bar"
+        "#)
+        .file("src/main.rs", r#"
+            fn main() {}
+        "#)
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("bar/src/lib.rs", r#"
+            invalid rust file, should not be compiled
+        "#);
+
+    p.cargo_process("build")
+        .exec_with_output()
+        .assert();
+
+    assert_that(&p.bin("foo"), existing_file());
+
+    assert_that(
+      cargo::util::process(p.bin("foo")),
+      execs());
+
+    let lockfile = p.root().join("Cargo.lock");
+    let lockfile = File::open(&lockfile).read_to_string().assert();
+    assert!(lockfile.as_slice().contains("bar"))
+})